#include "SharedFile.h"
#include <ext/stdio_sync_filebuf.h>

using namespace std;

SharedFile::SharedFile() {
	string path = this->getDefaultSharedFile();
	this->init(path);
}

SharedFile::SharedFile(string path) {
	this->init(path);
}

void SharedFile::init(string path) {
	// Abre el archivo en modo lectura - escritura binario.
	this->file.open(path.c_str(), ios::in | ios::out | ios::binary);

	// Determina si tuvo éxito la apertura del archivo.
	if (!this->file.is_open()) {
		// Limpia los flags de control de estado del archivo.
		this->file.clear();

		// Crea el archivo.
		this->file.open(path.c_str(), ios::out|ios::binary);
		this->file.close();

		// Reabre el archivo para lectura - escritura binario.
		this->file.open(path.c_str(), ios::in|ios::out|ios::binary);

		// Verifica que haya podido crear el archivo.
		if (!this->file.is_open()) {
		  // Arroja una excepcion.
		  throw string("El archivo no pudo ser abierto");
		}
	}

	typedef basic_filebuf<char, char_traits<char> > filebuf_t;
	filebuf_t* bbuf = dynamic_cast<filebuf_t*>(this->file.rdbuf());
	if (bbuf != NULL) {
		// This subclass is only there for accessing the FILE*. Ouuwww, sucks!
		struct my_filebuf : public basic_filebuf<char, char_traits<char> > {
			int fd() { return this->_M_file.fd(); }
		};

		this->fd = static_cast<my_filebuf*>(bbuf)->fd();
	}

	this->fl.l_type = F_WRLCK;
	this->fl.l_whence = SEEK_SET;
	this->fl.l_start = 0;
	this->fl.l_len = 0;
	this->fl.l_pid = getpid();
}

SharedFile::~SharedFile() {
	// Cierra el archivo.
	this->file.close();
}

string SharedFile::getDefaultSharedFile() {
	mkdir(ROOTDIRECTORY, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);

    stringstream stream;
	stream << ROOTDIRECTORY << DEFAULTSHAREDFILENAME;

    return stream.str();
}

SharedRecord SharedFile::getNextRecord() {
	SharedRecord record;

	// Verifica que el archivo esté abierto.
	if (this->file.is_open()) {
		// Lee del archivo un registro.
		this->file.read(reinterpret_cast<char*>(&record), sizeof(SharedRecord));

		// Chequea si se ha producido un error.
		if (this->file.fail()) {
			// Arroja una excepción ante la imposibilidad de leer un reg.
			throw string("No se pudo leer correctamente el registro");
		}

		return record;
	} else {
		// Arroja una excepción porque el archivo no está abierto.
		throw string("El archivo no está abierto");
	}
}

void SharedFile::addRecord(SharedRecord record) {
	// Verifica que el archivo esté abierto.
	if (this->file.is_open()) {
		// Escribe el registro en el archivo.
		this->file.write(reinterpret_cast<char*>(&record), sizeof(SharedRecord));

		// Chequea si se ha producido un error.
		if (this->file.fail()) {
		  // Arroja una excepción ante la imposibilidad de escribir el reg.
		  throw string("No se pudo escribir correctamente el registro");
		}
	}
	else {
		// Arroja una excepción porque el archivo no está abierto.
		throw string("El archivo no está abierto");
	}
}

void SharedFile::removeRecord(SharedRecord record) {
	// Verifica que el archivo este abierto.
	if (this->file.is_open()) {

		this->goTo(0);

		while(!this->isEOF()) {
			SharedRecord readRecord = this->getNextRecord();

			string pipe1 = readRecord.requestFifo;
			string path1 = readRecord.sharedFilePath;

			string pipe2 = record.requestFifo;
			string path2 = record.sharedFilePath;

			if ((pipe1 == pipe2) && (path1 == path2)) {
				strcpy(readRecord.requestFifo, "");
				strcpy(readRecord.sharedFilePath, "");
				int pos = this->currentPosition();
				this->goTo(pos - 1);
				this->addRecord(readRecord);
				this->goTo(pos);
			}
		}
	} else {
		// Arroja una excepción porque el archivo no está abierto.
		throw string("El archivo no está abierto");
	}
}

bool SharedFile::isEOF() {
	// Para comprobar el fin lee un char del buffer, sin retirarlo y lo
	// compara con el fin de archivo.
	int peeked = this->file.peek();
	bool isEOF = (peeked == char_traits<char>::eof());

	if (isEOF) {
		// Si llegó al fin del archivo limpia los flags.
		this->file.clear();
	}

	return isEOF;
}

long int SharedFile::currentPosition() {
	long int pos = 0;

	// Verifica que el archivo este abierto.
	if (this->file.is_open()) {
		// Calcula el número de registro segun la posicion del byte actual.
		pos = this->file.tellg() / sizeof(SharedRecord);
	} else {
		// Arroja una excepción porque el archivo no esta abierto.
		throw string("El archivo no está abierto");
	}

	return pos;
}

void SharedFile::goTo(long int position) {
	// Verifica que el archivo esté abierto.
	if (this->file.is_open()) {
		// Mueve la posición actual según sea el tamano del registro.
		this->file.seekg(position * sizeof(SharedRecord), ios_base::beg);

		// Chequea si se ha producido un error.
		if (this->file.fail()) {
		  // Arroja una excepción ante la imposibilidad de leer un reg.
		  throw string("No se pudo posicionar correctamente el registro");
		}
	} else {
		// Arroja una excepción porque el archivo no está abierto.
		throw string("El archivo no está abierto");
	}
}

void SharedFile::goToEnd() {
	// Verifica que el archivo este abierto.
	if (this->file.is_open()) {
		// Mueve la posición actual según sea el tamano del registro.
		this->file.seekg(0 * sizeof(SharedRecord), ios_base::end);

		// Chequea si se ha producido un error.
		if (this->file.fail()) {
		  // Arroja una excepción ante la imposibilidad de leer un reg.
		  throw string("No se pudo posicionar correctamente el registro");
		}
	} else {
		// Arroja una excepción porque el archivo no está abierto.
		throw string("El archivo no está abierto");
	}
}

int SharedFile::getWriteLock () {
	this->fl.l_type = F_WRLCK;
	int result = fcntl(this->fd, F_SETLKW, &(this->fl));
	return result;
}

int SharedFile::getReadLock () {
	this->fl.l_type = F_RDLCK;
	int result = fcntl(this->fd, F_SETLKW, &(this->fl));
	return result;
}

int SharedFile::unlock () {
	this->fl.l_type = F_UNLCK;
	int result = fcntl(this->fd, F_SETLK, &(this->fl));
	return result;
}
